package com.qqn.acknowledge.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @ClassName: ReflectVisit
 * @Description: ReflectVisit
 * @Author: 刘神仙
 * @Date: 2025-03-25 21:15
 */
public class ReflectVisit3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        // 获取class对象的三种方式
        // 第一种  类.class  字面常量方法获取class对象
        // 使用 类.class 不会触发类的初始化，只有在访问类的成员或者访问该类的实例时才会被触发
        // Class<User> clazz = User.class;

        // 第二种 使用对象的
        // User user = new User("Albert", 20);
        // 这里泛型参数用 通配符 因为这个Class对象是在运行时从User实例获取的，而 User实例的具体类型只能在运行时创建和确定
        // 在编译阶段无法准确的判断 Class 对象的确切类型
        // Class<?> clazz = user.getClass();

        // 第三种 使用 Class的 forName 静态方法，这种方法用于在运行时动态加载指定的类，并返回该类的 Class 对象实例，
        // 通常用于类名在编译时不可知的场景中
        // 通过这种方法获取的类的 Class 对象时会立即触发类的初始化，类的静态初始化块 static 块会被执行
        Class<?> clazz = Class.forName("com.qqn.acknowledge.reflection.User");
        // 接下来使用反射来操作类的实例，并对实例的字段值和方法进行访问和操作；
        // 在反射中通常通过获取类的构造器来创建实例，简单来说就是先从目标类的 Class 对象中选择一个合适的构造器
        // 然后通过调用它的 newInstance 方法，来创建对象
        Constructor<?> constructor = clazz.getDeclaredConstructor(); // 没有参数表示获取的是无参构造器
        constructor = clazz.getDeclaredConstructor(String.class, int.class); // 带参的
        // 创建的对象是在运行时动态生成的，因此无法在编译阶段确切地知道创建的对象的类型，所以使用 Object 类型接收
        Object obj = constructor.newInstance("Albert", 20);
        // if (obj instanceof User) { // 第一种转换方法
        //     User user = (User) obj;
        // }
        // 也可以用 class 对象的方法 这个方法内部是用的泛型，因此只能在编译期间生效
        // 因此只能用字面常量的方法获取 class 对象
        // Class<User> clazz = User.class;
        // User user1 = clazz.cast(obj);

        // 虽然以上展示了类型转换的方法，但这并不是使用反射的最佳实践，建议使用反射直接调用和操作对象的方法和字段 而不是先进行类型转换
        // 毕竟如果在编写阶段已经明确了要转换的类型，那么直接显示地调用更合适，而不必依赖于反射

        // 反射真正的价值在于，处理编译时未知的类型，从而编写更有通用性的代码

        // 接下来操作实例对象的字段值和方法，所有的方法其实和操作类的静态字段值调用静态方法是一样的，
        // 唯一的区别就是把之前的参数 null 变成对应的实例对象而已
        Field field = clazz.getDeclaredField("name");
        System.out.println(field.get(obj)); // 这里参数代表操作的是实例对象的字段值而不是静态字段值
        field = clazz.getDeclaredField("age");
        field.setAccessible(true);
        field.set(obj, 21);// 注意和授权的先后书写顺序
        System.out.println(field.get(obj));

        Method method = clazz.getDeclaredMethod("myPublicMethod");
        method.invoke(obj);
        method = clazz.getDeclaredMethod("myPrivateMethod");
        method.setAccessible(true);
        method.invoke(obj);
        method = clazz.getDeclaredMethod("myPrivateMethod",String.class, String.class);
        method.setAccessible(true);
        method.invoke(obj,"Hi there","!");

    }
}
